#include "BufferManager.h"
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstring> // 添加这行

namespace MiniDB
{

    // Constants
    const std::string BufferManager::DATA_FILE_NAME = "data.db";
    const std::string BufferManager::INDEX_FILE_NAME = "index.db";

    BufferManager::BufferManager(size_t buffer_size, ReplacementPolicy policy)
        : buffer_size_(buffer_size), policy_(policy), logging_enabled_(true)
    {

        buffer_pool_.resize(buffer_size_);

        // Initialize file handles
        auto &fm = FileManager::getInstance();
        std::cout << "Creating data file handle..." << std::endl;
        data_file_ = fm.createFile(DATA_FILE_NAME);
        if (data_file_) {
            std::cout << "Data file handle created successfully, is_open: " << data_file_->isOpen() << std::endl;
        } else {
            std::cout << "Failed to create data file handle!" << std::endl;
        }

        std::cout << "Creating index file handle..." << std::endl;
        index_file_ = fm.createFile(INDEX_FILE_NAME);
        if (index_file_) {
            std::cout << "Index file handle created successfully, is_open: " << index_file_->isOpen() << std::endl;
        } else {
            std::cout << "Failed to create index file handle!" << std::endl;
        }

        std::cout << "BufferManager initialized with " << buffer_size << " frames using "
                  << (policy == ReplacementPolicy::LRU ? "LRU" : "FIFO") << " policy" << std::endl;
    }

    BufferManager::~BufferManager()
    {
        flushAll();
        std::cout << "BufferManager destroyed" << std::endl;
    }

    std::shared_ptr<Page> BufferManager::getPage(int page_id)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        stats_.total_requests++;

        // Check if page is in buffer
        auto it = page_table_.find(page_id);
        if (it != page_table_.end())
        {
            size_t frame_index = it->second;
            auto &frame = buffer_pool_[frame_index];

            if (frame.is_valid)
            {
                stats_.cache_hits++;
                updateAccessTime(page_id);
                frame.page->pin();
                return frame.page;
            }
        }

        // Page not in buffer - cache miss
        stats_.cache_misses++;

        // Find a free frame or victim
        size_t frame_index = findVictimPage();
        if (frame_index == buffer_size_)
        {
            // No free frame available
            std::cerr << "Error: Buffer is full and no victim found" << std::endl;
            return nullptr;
        }

        auto &frame = buffer_pool_[frame_index];

        // If frame has a valid page, write it back if dirty
        if (frame.is_valid && frame.page->isDirty())
        {
            writePageToDisk(frame.page->getPageId());
        }

        // Create new page object first
        frame.page = std::make_shared<Page>(page_id);
        frame.is_valid = true;

        // Update page table before loading from disk
        page_table_[page_id] = frame_index;

        // Load new page from disk (this will copy data if page exists)
        loadPageFromDisk(page_id);

        // Update replacement policy structures
        updateAccessTime(page_id);

        frame.page->pin();
        return frame.page;
    }

    bool BufferManager::flushPage(int page_id)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        auto it = page_table_.find(page_id);
        if (it != page_table_.end())
        {
            size_t frame_index = it->second;
            auto &frame = buffer_pool_[frame_index];

            if (frame.is_valid && frame.page->isDirty())
            {
                writePageToDisk(page_id);
                frame.page->markClean();
                stats_.pages_written++;
                return true;
            }
        }

        return false;
    }

    bool BufferManager::flushAll()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        bool success = true;

        std::cout << "BufferManager::flushAll() called" << std::endl;
        std::cout << "Checking " << buffer_pool_.size() << " buffer frames for dirty pages..." << std::endl;

        for (size_t i = 0; i < buffer_pool_.size(); ++i)
        {
            auto &frame = buffer_pool_[i];  // Remove const to allow modification
            std::cout << "Frame " << i << ": valid=" << (frame.is_valid ? "true" : "false")
                     << ", has_page=" << (frame.page ? "true" : "false");

            if (frame.is_valid && frame.page)
            {
                std::cout << ", dirty=" << (frame.page->isDirty() ? "true" : "false")
                         << ", page_id=" << frame.page->getPageId();
            }
            std::cout << std::endl;

            if (frame.is_valid && frame.page && frame.page->isDirty())
            {
                std::cout << "Found dirty page " << frame.page->getPageId() << " in frame " << i << ", writing to disk..." << std::endl;
                writePageToDisk(frame.page->getPageId());
                frame.page->markClean();  // Now we can modify the page
                stats_.pages_written++;
            }
        }

        std::cout << "BufferManager::flushAll() completed" << std::endl;
        return success;
    }

    void BufferManager::releasePage(int page_id)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        auto it = page_table_.find(page_id);
        if (it != page_table_.end())
        {
            size_t frame_index = it->second;
            auto &frame = buffer_pool_[frame_index];

            if (frame.is_valid)
            {
                frame.page->unpin();
            }
        }
    }

    void BufferManager::resize(size_t new_size)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        if (new_size < buffer_pool_.size())
        {
            // Shrink buffer - flush dirty pages in frames to be removed
            for (size_t i = new_size; i < buffer_pool_.size(); ++i)
            {
                auto &frame = buffer_pool_[i];
                if (frame.is_valid)
                {
                    if (frame.page->isDirty())
                    {
                        writePageToDisk(frame.page->getPageId());
                    }
                    // Remove from page table and replacement structures
                    page_table_.erase(frame.page->getPageId());
                    if (policy_ == ReplacementPolicy::LRU)
                    {
                        lru_list_.erase(lru_map_[frame.page->getPageId()].position);
                        lru_map_.erase(frame.page->getPageId());
                    }
                    else
                    {
                        fifo_list_.erase(fifo_map_[frame.page->getPageId()]);
                        fifo_map_.erase(frame.page->getPageId());
                    }
                }
            }
        }

        buffer_pool_.resize(new_size);
        buffer_size_ = new_size;
    }

    void BufferManager::setReplacementPolicy(ReplacementPolicy policy)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        if (policy != policy_)
        {
            policy_ = policy;

            // Clear existing replacement structures
            lru_list_.clear();
            lru_map_.clear();
            fifo_list_.clear();
            fifo_map_.clear();

            // Rebuild structures for current pages
            for (const auto &pair : page_table_)
            {
                int page_id = pair.first;
                if (policy_ == ReplacementPolicy::LRU)
                {
                    lru_list_.push_front(page_id);
                    lru_map_[page_id].position = lru_list_.begin();
                }
                else
                {
                    fifo_list_.push_back(page_id);
                    fifo_map_[page_id] = --fifo_list_.end();
                }
            }

            std::cout << "Replacement policy changed to "
                      << (policy == ReplacementPolicy::LRU ? "LRU" : "FIFO") << std::endl;
        }
    }

    int BufferManager::findVictimPage()
    {
        // First, look for unpinned frames
        for (size_t i = 0; i < buffer_pool_.size(); ++i)
        {
            if (!buffer_pool_[i].is_valid || buffer_pool_[i].page->getPinCount() == 0)
            {
                return i;
            }
        }

        // If no unpinned frames, use replacement policy
        if (policy_ == ReplacementPolicy::LRU)
        {
            // Find LRU page that is not pinned
            for (auto it = lru_list_.rbegin(); it != lru_list_.rend(); ++it)
            {
                int page_id = *it;
                auto pt_it = page_table_.find(page_id);
                if (pt_it != page_table_.end())
                {
                    size_t frame_index = pt_it->second;
                    if (buffer_pool_[frame_index].page->getPinCount() == 0)
                    {
                        return frame_index;
                    }
                }
            }
        }
        else
        { // FIFO
            // Find oldest page that is not pinned
            for (auto it = fifo_list_.begin(); it != fifo_list_.end(); ++it)
            {
                int page_id = *it;
                auto pt_it = page_table_.find(page_id);
                if (pt_it != page_table_.end())
                {
                    size_t frame_index = pt_it->second;
                    if (buffer_pool_[frame_index].page->getPinCount() == 0)
                    {
                        return frame_index;
                    }
                }
            }
        }

        return buffer_size_; // No victim found
    }

    void BufferManager::loadPageFromDisk(int page_id)
    {
        char buffer[PAGE_SIZE];
        if (data_file_->readBlock(page_id, buffer, PAGE_SIZE))
        {
            // Page exists on disk - copy data to the newly created page
            std::cout << "Loading existing page " << page_id << " from disk" << std::endl;

            // Find the frame for this page
            auto it = page_table_.find(page_id);
            if (it != page_table_.end())
            {
                size_t frame_index = it->second;
                auto &frame = buffer_pool_[frame_index];

                if (frame.is_valid && frame.page)
                {
                    // Copy the data from disk to the page
                    char* page_data = const_cast<char*>(frame.page->getData());
                    std::memcpy(page_data, buffer, PAGE_SIZE);
                    std::cout << "Data loaded into page " << page_id << std::endl;
                }
            }

            stats_.pages_read++;
        }
        else
        {
            // New page - initialize to zero
            std::cout << "Creating new page " << page_id << " (not found on disk)" << std::endl;
            std::memset(buffer, 0, PAGE_SIZE);
        }
    }

    void BufferManager::writePageToDisk(int page_id)
    {
        auto it = page_table_.find(page_id);
        if (it != page_table_.end())
        {
            size_t frame_index = it->second;
            const auto &frame = buffer_pool_[frame_index];

            if (frame.is_valid && frame.page)
            {
                // Get the actual page data
                const char* page_data = frame.page->getData();
                std::cout << "Writing page " << page_id << " to disk (" << frame.page->getUsedSpace() << " bytes used)" << std::endl;

                // Check if data_file_ is valid
                if (!data_file_)
                {
                    std::cout << "Error: data_file_ is null!" << std::endl;
                    return;
                }

                if (!data_file_->isOpen())
                {
                    std::cout << "Error: data_file_ is not open!" << std::endl;
                    return;
                }

                // Write page data to disk
                bool success = data_file_->writeBlock(page_id, page_data, PAGE_SIZE);
                if (success) {
                    std::cout << "Successfully wrote page " << page_id << " to disk" << std::endl;
                } else {
                    std::cout << "Failed to write page " << page_id << " to disk" << std::endl;
                }
            }
        }
    }

    void BufferManager::updateAccessTime(int page_id)
    {
        if (policy_ == ReplacementPolicy::LRU)
        {
            auto it = lru_map_.find(page_id);
            if (it != lru_map_.end())
            {
                // Move to front of LRU list
                lru_list_.erase(it->second.position);
                lru_list_.push_front(page_id);
                it->second.position = lru_list_.begin();
            }
            else
            {
                // Add new entry
                lru_list_.push_front(page_id);
                lru_map_[page_id].position = lru_list_.begin();
            }
        }
        else
        { // FIFO
            auto it = fifo_map_.find(page_id);
            if (it == fifo_map_.end())
            {
                // Add new entry
                fifo_list_.push_back(page_id);
                fifo_map_[page_id] = --fifo_list_.end();
            }
        }

        // Update frame access time
        auto pt_it = page_table_.find(page_id);
        if (pt_it != page_table_.end())
        {
            buffer_pool_[pt_it->second].last_access_time = std::chrono::steady_clock::now();
        }
    }

    void BufferManager::logReplacement(int victim_page_id, int new_page_id)
    {
        if (logging_enabled_)
        {
            std::cout << "[REPLACEMENT] Victim: Page " << victim_page_id
                      << " -> New: Page " << new_page_id << std::endl;
            stats_.replacements++;
        }
    }

} // namespace MiniDB
